﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2013-12-11 14:55
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using BassDrop.Core.Common.Cloud;
using BassDrop.Core.Common.Data.IO;
using BassDrop.Core.Common.Data.IO.Portable;
using BassDrop.Core.Common.General.Events;
using BassDrop.Core.Common.Localization;
using BassDrop.Core.Common.Media.Playlists;
using BassDrop.Core.Common.Services;

namespace BassDrop.Core.Common.Data
{
	/// <summary>
	/// File representation of a Playlist
	/// </summary>
	public class PlaylistFile : RemoteFileObjectBase<Playlist>
	{
		/// <summary>
		/// Automatically save the playlist to file when it changes
		/// </summary>
		public bool AutoSave
		{
			get;
			set;
		}

		/// <summary>
		/// Automatically push the playlist to the cloud when it changes
		/// </summary>
		public bool AutoPush
		{
			get;
			set;
		}

		/// <summary>
		/// Signals that the playlist is currently being loaded from file
		/// </summary>
		private volatile bool m_loading = false;

		private object m_lock = new object();

		/// <summary>
		/// Gets the lock used in this object
		/// </summary>
		protected override object Lock
		{
			get
			{
				if ( RepresentedObject != null )
				{
					return ((ICollection) RepresentedObject).SyncRoot;
				}
				else
				{
					return m_lock; // TODO: ebből nem lehet baj?
				}
			}
		}

		/// <summary>
		/// Creates a new LOCAL ONLY playlist file
		/// </summary>
		/// <param name="representedPlaylist">the initial state of the represented playlist (or null)</param>
		/// <param name="filePath">the path of the designated file of this object in the system</param>
		/// <param name="autoSave">
		///		automatically save the playlist to file when it changes. Setting it to true immediately saves the passed representedPlaylist unless it is null. (default: false)
		/// </param>
		public PlaylistFile( Playlist representedPlaylist, Uri filePath, bool autoSave = false )
			: this( representedPlaylist, filePath, null, null, autoSave )
		{
		}

		/// <summary>
		/// Creates a new LOCAL AND REMOTE playlist file
		/// </summary>
		/// <param name="representedPlaylist">the initial state of the represented playlist (or null)</param>
		/// <param name="filePath">the path of the designated file of this object in the system</param>
		/// <param name="remotePath">the path of the designated file of this object in the cloud</param>
		/// <param name="cloudConnection">the object providing the connection to the cloud storing this file</param>
		/// <param name="autoSave">
		///		automatically save the playlist to file when it changes. Setting it to true immediately saves the passed representedPlaylist unless it is null. (default: false)
		/// </param>
		/// <param name="autoPush">
		///		automatically push the playlist to the cloud when it changes. Setting it to true implicitly sets autoSave to true as well.
		///		Setting it to true immediately saves the passed representedPlaylist unless it is null. (default: false)
		///	</param>
		public PlaylistFile( Playlist representedPlaylist, Uri filePath, string remotePath, CloudConnectionBase cloudConnection, bool autoSave = false, bool autoPush = false )
			: base( ref representedPlaylist, filePath, remotePath, cloudConnection )
		{
			AutoSave = autoSave || autoPush;
			AutoPush = autoPush;
			BindListeners();
		}

		/// <summary>
		/// Set the contents of this object to the contents of a new, empty one.
		/// Called when neither a remote, nor a local file exists.
		/// </summary>
		protected override void CreateNew()
		{
			lock ( Lock )
			{
				RepresentedObject = new Playlist( "New playlist" );
				BindListeners();
			}
		}

		/// <summary>
		/// Synchronizes the contents of this object from the file on the passed uri
		/// </summary>
		/// <param name="uri"></param>
		protected override void SynchFrom( Uri uri )
		{
			lock ( Lock )
			{
				if ( RepresentedObject.SelectedItem != null
					&& RepresentedObject.SelectedItem.CurrentPlayState != PlaylistItemBase.PlayState.Error
					&& RepresentedObject.SelectedItem.CurrentPlayState != PlaylistItemBase.PlayState.Finished
					&& RepresentedObject.SelectedItem.CurrentPlayState != PlaylistItemBase.PlayState.Stopped )
				{
					return;
				}

				bool otherNewer = ServiceCenter.FileHandler.GetMetadata( uri.LocalPath ).LastWriteTimeUtc > ServiceCenter.FileHandler.GetMetadata( FilePath.LocalPath ).LastWriteTimeUtc;

				if ( otherNewer )
				{
					Load( uri );
				}
			}
		}

		/// <summary>
		/// Loads the contents of the object from its designated file
		/// </summary>
		public override void Load()
		{
			lock ( Lock )
			{
				Load( FilePath );
			}
		}

		/// <summary>
		/// Loads the contents of the object from the file on the passed path
		/// </summary>
		/// <param name="path"></param>
		private void Load( Uri path )
		{
			Playlist temp = PlaylistIO.Read( path );

			lock ( Lock )
			{
				m_loading = true;

				try
				{
					if ( RepresentedObject == null )
					{
						RepresentedObject = temp;
						BindListeners();
					}
					else
					{
						RepresentedObject.SyncFrom( temp );
					}
				}
				finally
				{
					m_loading = false;
				}
			}
		}

		/// <summary>
		/// Saves the contents of the object into its designated file
		/// </summary>
		public override void Save()
		{
			Playlist pls;
			Uri uri;

			lock ( Lock )
			{
				pls = RepresentedObject;
				uri = FilePath;
			}

			if ( pls == null )
			{
				throw new InvalidOperationException( Strings.ExceptionPlaylistToSaveIsNull );
			}

			PlaylistIO.Write( pls, uri );
		}

		/// <summary>
		/// Calls Save or ScheduleSynchronize when / as appropriate now
		/// </summary>
		private void SaveIfNeeded()
		{
			bool needed;

			lock ( Lock )
			{
				needed = !m_loading && RepresentedObject != null && AutoSave;

				if ( needed && RepresentedObject.TransactionGoing )
				{
					needed = false;
					RepresentedObject.TransactionEnded -= RepresentedObject_TransactionEnded;
					RepresentedObject.TransactionEnded += RepresentedObject_TransactionEnded;
				}
			}

			if ( needed )
			{
				DoSave();
			}
		}

		void RepresentedObject_TransactionEnded( object sender, CustomDataEventArgs<bool> e )
		{
			if ( e.Data )
			{
				DoSave();
				RepresentedObject.TransactionEnded -= RepresentedObject_TransactionEnded;
			}
		}

		/// <summary>
		/// Calls Save and also ScheduleSynchronize if appropriate
		/// </summary>
		private void DoSave()
		{
			Save();

			if ( AutoPush && Synchronizable )
			{
				ScheduleSynchronize();
			}
		}

		/// <summary>
		/// Binds the event listeners to the current RepresentedObject
		/// </summary>
		private void BindListeners()
		{
			lock ( Lock )
			{
				if ( RepresentedObject != null )
				{
					RepresentedObject.ItemCollectionChanged += PlaylistFile_ItemCollectionChanged;
					RepresentedObject.ItemChanged += PlaylistFile_ItemChanged;
					RepresentedObject.TrackChanged += PlaylistFile_TrackChanged;
				}
			}
		}

		/// <summary>
		/// Called when the RepresentedObject has changed.
		/// This is not an event handler.
		/// </summary>
		/// <param name="oldValue">the old value of the RepresentedObject</param>
		/// <param name="newValue">the new value of the RepresentedObject</param>
		protected override void RepresentedObjectChanged( Playlist oldValue, Playlist newValue )
		{
			base.RepresentedObjectChanged( oldValue, newValue );

			if ( oldValue != null )
			{
				oldValue.PlaylistFile = null;
			}

			if ( newValue != null )
			{
				newValue.PlaylistFile = this;
			}
		}

		void PlaylistFile_TrackChanged( object sender, PropertyChangedEventArgs e )
		{
			SaveIfNeeded();
		}

		void PlaylistFile_ItemChanged( object sender, CollectionItemChangedEventArgs e )
		{
			if ( e.PropertyChangedEventArgs != null && new[] { "LibraryId", "FilePath" }.Contains( e.PropertyChangedEventArgs.PropertyName ) ) // TODO: figyelt elemek?
			{
				SaveIfNeeded();
			}
		}

		void PlaylistFile_ItemCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
		{
			SaveIfNeeded();
		}
	}
}
